/*
 * CopyRight(C) Kenbin
 */

#include "lsg_config.h"

LSG::CFileBucket::CFileBucket(const char *base_path, int block_size, 
	int block_count, int loaded_max)
{
    m_errid = 0;

    /* if set m_errid, caller must handle error */
    if(block_size <=0) {
	m_errid = 1;
    } else if(block_count <= 0 || GetPower(block_count) < 3) {
	m_errid = 2;
    } else if(0x7FFFFFFF / block_count < block_size) {
	m_errid = 3;
    } else if(loaded_max <= 0) {
	m_errid = 4;
    } else if(access(base_path, R_OK | W_OK | X_OK | F_OK) < 0) {
	if(mkdir(base_path, DIR_ACESS_AUTH) < 0) {
	    m_errid = 5;
	    m_errno = errno;
	}
    }

    if(m_errid != 0) {
	return;
    }

    strncpy(m_base_path, base_path, sizeof(m_base_path)-1);

    m_block_size = block_size;
    m_block_count = block_count;

    m_loaded_max = loaded_max > MAX_FILE_CNT ? MAX_FILE_CNT : loaded_max;
    m_loaded_count = 0;

    for(int i = 0; i < MAX_FILE_CNT; i ++)
    {
	m_buddyfiles[i].obj = NULL;
	m_buddyfiles[i].opentime = 0;
	m_buddyfiles[i].lasttime = 0;
	m_buddyfiles[i].access_count = 0;

	m_buddyfiles[i].flag = verify_buddyfile(i, &m_buddyfiles[i].alloced);
    }

    m_writer = WRITER_NOSET;
}

LSG::CFileBucket::~CFileBucket()
{
    for(int i = 0; i < MAX_FILE_CNT; i ++)
    {
	if (m_buddyfiles[i].obj != NULL)
	{
	    delete m_buddyfiles[i].obj;
	    m_buddyfiles[i].obj = NULL;
	}
    }
}

int LSG::CFileBucket::Save(char *physic_id, char *data, 
	int data_len, int reserve)
{
    physic_id[0] = '\0';
    if(data_len <= 0) {
	return LSG_INVALID_DATA_LEN;
    }

    /* choose a buddyfile */
    if(m_writer == WRITER_NOSET && try_alloc_writer() == WRITER_NOSET 
	    && alloc_writer() == WRITER_NOSET) {
	return LSG_CHOOSE_WRITER_FAIL;
    }

    if(m_writer < 0 || m_writer >= MAX_FILE_CNT) {
	return LSG_INVALID_WRITER;
    }

    if(m_buddyfiles[m_writer].flag != FILESEQ_LOADED) {
	return LSG_INVALID_WRITER_FLAG;
    }

    if(m_buddyfiles[m_writer].obj == NULL) {
	return LSG_INVALID_WRITER_OBJECT;
    }

    /* save data into choosed-buddyfile */
    int buddybit_offset = m_buddyfiles[m_writer].obj->Save(data, data_len, reserve);

    /* save fail, try another opened-buddyfile */
    if(buddybit_offset < 0 && try_alloc_writer() >=0 && m_buddyfiles[m_writer].obj) {
	buddybit_offset = m_buddyfiles[m_writer].obj->Save(data, data_len, reserve);
    }

    /* save fail, try a new buddyfile */
    if(buddybit_offset < 0 && alloc_writer() >= 0 && m_buddyfiles[m_writer].obj) {
	buddybit_offset = m_buddyfiles[m_writer].obj->Save(data, data_len, reserve);
    }

    m_buddyfiles[m_writer].alloced = m_buddyfiles[m_writer].obj->BuddyBit()->Alloced();
    m_buddyfiles[m_writer].lasttime = time(NULL);
    m_buddyfiles[m_writer].access_count ++;

    int e = -1;
    if(buddybit_offset < 0) {
	e = buddybit_offset;
    } else {
	e = 0;
	snprintf(physic_id, PHYSICID_LEN, "%d.%d.%d", 
		m_block_size, m_writer, buddybit_offset);
    }

    return e;
}

int LSG::CFileBucket::Update(const char *physic_id, int byte_offset, 
	char * data, int data_len, int reserve)
{
    if(data_len < 0) {
	return LSG_INVALID_DATA_LEN;
    }

    int file_seq, buddybit_offset;
    CBuddyFile *buddyfile_obj = NULL;
    int e = choose_buddyfile(physic_id, &file_seq, &buddybit_offset, &buddyfile_obj);
    if(e < 0) {
	return e;
    }

    e = buddyfile_obj->Update(buddybit_offset, byte_offset, data, data_len, reserve);

    m_buddyfiles[file_seq].alloced = m_buddyfiles[file_seq].obj->BuddyBit()->Alloced();
    m_buddyfiles[file_seq].lasttime = time(NULL);
    m_buddyfiles[file_seq].access_count ++;

    return e;
}

int LSG::CFileBucket::Read(const char *physic_id, char **data, 
	int *data_len, int *reserve)
{
    *data_len = 0;

    int file_seq, buddybit_offset;
    CBuddyFile *buddyfile_obj = NULL;
    int e = choose_buddyfile(physic_id, &file_seq, &buddybit_offset, &buddyfile_obj);
    if(e < 0) {
	return e;
    }

    e = buddyfile_obj->Read(buddybit_offset, data, data_len, reserve);

    m_buddyfiles[file_seq].alloced = m_buddyfiles[file_seq].obj->BuddyBit()->Alloced();
    m_buddyfiles[file_seq].lasttime = time(NULL);
    m_buddyfiles[file_seq].access_count ++;

    return e;
}

int LSG::CFileBucket::Delete(const char *physic_id)
{
    int file_seq, buddybit_offset;
    CBuddyFile *buddyfile_obj = NULL;
    int e = choose_buddyfile(physic_id, &file_seq, &buddybit_offset, &buddyfile_obj);
    if(e < 0) {
	return e;
    }

    e = buddyfile_obj->Delete(buddybit_offset);

    m_buddyfiles[file_seq].alloced = m_buddyfiles[file_seq].obj->BuddyBit()->Alloced();
    m_buddyfiles[file_seq].lasttime = time(NULL);
    m_buddyfiles[file_seq].access_count ++;

    return e;
}

int LSG::CFileBucket::verify_buddyfile(int file_seq, int *alloced)
{
    int flag;

    char filename[256] = {0};
    get_filename(file_seq, filename, sizeof(filename));

    if(access(filename, F_OK) < 0)
    {
	flag = FILESEQ_NOUSE;
	*alloced = 0;
    }
    else if(access(filename, R_OK | W_OK) < 0)
    {
	flag = FILESEQ_ERR;
	*alloced = 0;
    }
    else
    {
	int a = VerifyHeadBlock(filename, m_block_size, m_block_count);
	if(a < 0)
	{
	    flag = FILESEQ_ERR;
	    *alloced = 0;
	}
	else
	{
	    flag = FILESEQ_USED;
	    *alloced = a;
	}
    }

    if(flag == FILESEQ_NOUSE) {
	lsg_logging(LSG_LOG_INFO, "verify_buddyfile: filebucket:%s fileseq%d:%s no used",
		m_base_path, file_seq, filename);
    } else if(flag == FILESEQ_USED) {
	lsg_logging(LSG_LOG_INFO, "verify_buddyfile: filebucket:%s fileseq%d:%s used %d blocks",
		m_base_path, file_seq, filename, *alloced);
    } else if(flag == FILESEQ_ERR) {
	lsg_logging(LSG_LOG_ERR, "verify_buddyfile: filebucket:%s fileseq%d:%s %s",
		m_base_path, file_seq, filename, strerror(errno));
    }

    return flag;
}

int LSG::CFileBucket::try_alloc_writer()
{
    m_writer = WRITER_NOSET;

    int file_seq = -1, alloced = 0x7FFFFFFF;
    for(int i = 0; i < MAX_FILE_CNT; i ++)
    {
	if(m_buddyfiles[i].flag == FILESEQ_LOADED || m_buddyfiles[i].flag == FILESEQ_USED)
	{
	    if(m_buddyfiles[i].alloced < alloced)
	    {
		file_seq = i;
		alloced = m_buddyfiles[i].alloced;
	    }
	}
    }

    if(file_seq >= 0) {
	if(m_buddyfiles[file_seq].flag == FILESEQ_LOADED || load_buddyfile(file_seq) >= 0) {
	    m_writer = file_seq;
	}
    }

    return m_writer;
}

int LSG::CFileBucket::alloc_writer()
{
    m_writer = WRITER_NOSET;

    int file_seq = -1;
    for(int i = 0; i < MAX_FILE_CNT; i ++)
    {
	if(m_buddyfiles[i].flag == FILESEQ_NOUSE)
	{
	    file_seq = i;
	    break;
	}
    }

    if(file_seq >= 0) {
	if(load_buddyfile(file_seq) >= 0) {
	    m_writer = file_seq;
	}
    }

    return m_writer;
}

int LSG::CFileBucket::choose_buddyfile(const char *physic_id, int *file_seq,
	int *buddybit_offset, CBuddyFile **buddyfile_obj)
{
    int block_size;
    DecodePhysicID(physic_id, &block_size, file_seq, buddybit_offset);

    if(block_size != m_block_size) {
	return LSG_INVALID_BLOCK_SIZE;
    } else if(*file_seq < 0 || *file_seq >= MAX_FILE_CNT) {
	return LSG_INVALID_FILE_SEQ;
    } else if(*buddybit_offset < 0 || *buddybit_offset > m_block_count) {
	return LSG_INVALID_BUDDYBIT_OFFSET;
    }

    if(m_buddyfiles[*file_seq].flag == FILESEQ_LOADED) {
	*buddyfile_obj = m_buddyfiles[*file_seq].obj;
    } else if(m_buddyfiles[*file_seq].flag == FILESEQ_USED && load_buddyfile(*file_seq) == 0) {
	*buddyfile_obj = m_buddyfiles[*file_seq].obj;
    }

    if(*buddyfile_obj) {
	return 0;
    } else {
	return LSG_INVALID_BUDDYFILE_OBJECT;
    }
}

int LSG::CFileBucket::load_buddyfile(int file_seq)
{
    if(m_buddyfiles[file_seq].flag != FILESEQ_NOUSE && m_buddyfiles[file_seq].flag != FILESEQ_USED) {
	return LSG_INVALID_BUDDYFILE_FLAG;
    }

    int e = -1;
    if(m_loaded_count < m_loaded_max)
    {
	e = open_buddyfile(file_seq);
	if(e == 0) {
	    m_loaded_count ++;
	} else {
	    return e;
	}
    }
    else
    {
	int outer = -1, lasttime = 0x7FFFFFFF;
	for(int i = 0; i < MAX_FILE_CNT; i ++)
	{
	    if(i != m_writer && m_buddyfiles[i].flag == FILESEQ_LOADED && m_buddyfiles[i].lasttime < lasttime)
	    {
		outer = i;
		lasttime = m_buddyfiles[i].lasttime;
	    }
	}

	if(outer < 0) {
	    return LSG_BUDDYFILE_MAYBE_DAMAGED;
	}

	e = close_buddyfile(outer);
	if(e < 0) {
	    return e;
	}

	m_loaded_count --;

	e = open_buddyfile(file_seq);
	if(e == 0) {
	    m_loaded_count ++;
	} else {
	    return e;
	}
    }

    return e;
}

int LSG::CFileBucket::unload_buddyfile(int file_seq)
{
    if(m_buddyfiles[file_seq].flag != FILESEQ_LOADED || m_buddyfiles[file_seq].obj == NULL) {
	return LSG_INVALID_BUDDYFILE_FLAG;
    }

    int e = close_buddyfile(file_seq);
    if(e < 0) {
	return e;
    }

    m_loaded_count --;

    if(file_seq == m_writer) {
	m_writer = WRITER_NOSET;
    }
}

int LSG::CFileBucket::open_buddyfile(int file_seq)
{
    if(m_buddyfiles[file_seq].flag != FILESEQ_NOUSE && m_buddyfiles[file_seq].flag != FILESEQ_USED) {
	return LSG_INVALID_BUDDYFILE_FLAG;
    } else if(m_buddyfiles[file_seq].obj != NULL) {
	LSG_INVALID_BUDDYFILE_OBJECT;
    }

    char filename[256] = {0};
    get_filename(file_seq, filename, sizeof(filename));

    m_buddyfiles[file_seq].obj = new CBuddyFile(filename);
    if(m_buddyfiles[file_seq].obj->Error())
    {
	m_buddyfiles[file_seq].alloced = 0;
	m_buddyfiles[file_seq].flag = FILESEQ_ERR;
	m_buddyfiles[file_seq].opentime = 0;
	m_buddyfiles[file_seq].lasttime = 0;
	m_buddyfiles[file_seq].access_count = 0;

	delete m_buddyfiles[file_seq].obj;
	m_buddyfiles[file_seq].obj = NULL;

	lsg_logging(LSG_LOG_ERR, "open_buddyfile: filebucket:%s fileseq%d:%s %s",
	       	m_base_path, file_seq, filename, m_buddyfiles[file_seq].obj->ErrMsg());

	return LSG_CREATE_OBJECT_ERR;
    }
    else
    {
	m_buddyfiles[file_seq].alloced = m_buddyfiles[file_seq].obj->BuddyBit()->Alloced();
	m_buddyfiles[file_seq].flag = FILESEQ_LOADED;
	m_buddyfiles[file_seq].opentime = time(NULL);
	m_buddyfiles[file_seq].lasttime = 0;
	m_buddyfiles[file_seq].access_count = 0;

	lsg_logging(LSG_LOG_INFO, "open_buddyfile: filebucket:%s fileseq%d:%s",
	       	m_base_path, file_seq, filename);

	return 0;
    }
}

int LSG::CFileBucket::close_buddyfile(int file_seq)
{
    if(m_buddyfiles[file_seq].flag != FILESEQ_LOADED) {
	return LSG_INVALID_BUDDYFILE_FLAG;
    } else if(m_buddyfiles[file_seq].obj == NULL) {
	return LSG_INVALID_BUDDYFILE_OBJECT;
    }

    m_buddyfiles[file_seq].alloced = m_buddyfiles[file_seq].obj->BuddyBit()->Alloced();
    m_buddyfiles[file_seq].flag = FILESEQ_USED;
    m_buddyfiles[file_seq].opentime = 0;
    m_buddyfiles[file_seq].lasttime = 0;
    m_buddyfiles[file_seq].access_count = 0;

    delete m_buddyfiles[file_seq].obj;
    m_buddyfiles[file_seq].obj = NULL;

    char filename[256] = {0};
    get_filename(file_seq, filename, sizeof(filename));

    lsg_logging(LSG_LOG_INFO, "close_buddyfile: filebucket:%s fileseq%d:%s",
	    m_base_path, file_seq, filename);
}

char *LSG::CFileBucket::get_filename(int file_seq,
	char *filename, int len)
{
    snprintf(filename, len, "%s/%d.%d.%d", m_base_path, 
	    m_block_size, m_block_count, file_seq);
    return filename;
}
